#target illustrator

/**
 * Batch process PDF files to release clipping masks, ungroup, and remove empty objects.
 * Compatible with Adobe Illustrator 2025.
 */
(function () {
    if (app.name !== "Adobe Illustrator") {
        alert("请在 Adobe Illustrator 中运行此脚本。");
        return;
    }

    var originalInteractionLevel = app.userInteractionLevel;
    var originalCoordinateSystem = app.coordinateSystem;

    app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;
    app.coordinateSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM;

    try {
        run();
    } catch (error) {
        alert("脚本执行失败: " + error.message);
    } finally {
        app.userInteractionLevel = originalInteractionLevel;
        app.coordinateSystem = originalCoordinateSystem;
    }
})();

function run() {
    var inputFolder = Folder.selectDialog("请选择 PDF 输入文件夹");
    if (!inputFolder) {
        return;
    }

    var outputFolder = Folder.selectDialog("请选择输出文件夹");
    if (!outputFolder) {
        return;
    }

    var pdfFiles = inputFolder.getFiles(function (item) {
        return item instanceof File && /\.pdf$/i.test(item.name);
    });

    if (!pdfFiles || pdfFiles.length === 0) {
        alert("选定的输入文件夹中没有 PDF 文件。");
        return;
    }

    var processed = 0;
    var failures = [];

    for (var i = 0; i < pdfFiles.length; i++) {
        var pdfFile = pdfFiles[i];
        try {
            processPdfFile(pdfFile, outputFolder);
            processed++;
        } catch (fileError) {
            failures.push(pdfFile.displayName + ": " + fileError.message);
        }
    }

    showSummary(processed, pdfFiles.length, failures);
}

function processPdfFile(pdfFile, outputFolder) {
    var document = app.open(pdfFile);

    try {
        unlockAndReveal(document);
        releaseAllClippingMasks(document);
        ungroupAll(document);
        releaseAllClippingMasks(document);
        cleanDocument(document);

        app.selection = null;
        saveAsPdf(document, outputFolder, pdfFile.displayName);
    } finally {
        document.close(SaveOptions.DONOTSAVECHANGES);
    }
}

function unlockAndReveal(document) {
    app.selection = null;
    try {
        document.selection = null;
    } catch (_) {}

    try {
        app.executeMenuCommand("unlockAll");
    } catch (_) {}
    try {
        app.executeMenuCommand("showAll");
    } catch (_) {}
}

function releaseAllClippingMasks(document) {
    var maxAttempts = 20;
    for (var attempt = 0; attempt < maxAttempts; attempt++) {
        if (countClippingMasks(document) === 0) {
            break;
        }

        selectAll(document);
        try {
            app.executeMenuCommand("releaseMask");
        } catch (_) {
            break;
        }
    }

    // Fallback: remove clipping flag from any remaining items
    var pageItems = document.pageItems;
    for (var i = 0; i < pageItems.length; i++) {
        var item = pageItems[i];
        if (!item) {
            continue;
        }
        try {
            if (item.clipping) {
                item.clipping = false;
            }
        } catch (_) {}
    }

    app.selection = null;
}

function countClippingMasks(document) {
    var count = 0;
    var pageItems = document.pageItems;
    for (var i = 0; i < pageItems.length; i++) {
        var item = pageItems[i];
        try {
            if (item && item.clipping) {
                count++;
            }
        } catch (_) {}
    }
    return count;
}

function ungroupAll(document) {
    var maxAttempts = 50;
    for (var attempt = 0; attempt < maxAttempts; attempt++) {
        if (!document.groupItems || document.groupItems.length === 0) {
            break;
        }

        var before = document.groupItems.length;
        selectAll(document);

        try {
            app.executeMenuCommand("ungroup");
        } catch (_) {
            break;
        }

        if (!document.groupItems || document.groupItems.length === 0) {
            break;
        }

        if (document.groupItems.length >= before) {
            // No further change, abort to avoid infinite loop
            break;
        }
    }
}

function cleanDocument(document) {
    removeEmptyPathItems(document);
    removeStrayPathItems(document);
    removeEmptyTextFrames(document);
}

function removeEmptyPathItems(document) {
    var items = document.pathItems;
    for (var i = items.length - 1; i >= 0; i--) {
        var item = items[i];
        if (!item || item.locked || item.guides || item.clipping) {
            continue;
        }
        if (!item.filled && !item.stroked) {
            item.remove();
            continue;
        }
        if (item.closed) {
            try {
                if (Math.abs(item.area) < 0.01) {
                    item.remove();
                    continue;
                }
            } catch (_) {}
        }
    }
}

function removeStrayPathItems(document) {
    var items = document.pathItems;
    for (var i = items.length - 1; i >= 0; i--) {
        var item = items[i];
        if (!item || item.locked || item.guides || item.clipping) {
            continue;
        }
        if (item.pathPoints && item.pathPoints.length <= 1) {
            item.remove();
        }
    }
}

function removeEmptyTextFrames(document) {
    var frames = document.textFrames;
    for (var i = frames.length - 1; i >= 0; i--) {
        var frame = frames[i];
        if (!frame || frame.locked) {
            continue;
        }
        if (frame.characters.length === 0) {
            frame.remove();
        }
    }
}

function selectAll(document) {
    app.selection = null;
    try {
        document.selection = null;
    } catch (_) {}

    try {
        app.executeMenuCommand("selectall");
    } catch (_) {}
}

function saveAsPdf(document, outputFolder, fileName) {
    var outputFile = new File(outputFolder.fsName + "/" + fileName);
    var pdfOptions = new PDFSaveOptions();
    pdfOptions.preserveEditability = true;
    document.saveAs(outputFile, pdfOptions);
}

function showSummary(processed, total, failures) {
    var message = "已处理 PDF 数量: " + processed + "/" + total;
    if (failures.length > 0) {
        message += "\n以下文件处理失败：\n" + failures.join("\n");
    }
    alert(message);
}
